//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "kminner.h"
#include <kmemdet.h>
#include <_hal.h>

#if defined(KERNEL_MEMORY_DETECT)

typedef struct KMemCheckPoint
{
    PKMemBlock *curCPHashList;
    UInt32 uBlockCount;
    UInt32 uAllocedSize;
    UInt32 uPageAllotTimes;
    UInt32 uAllocedPageCount;
    UInt32 IsValid;
} KMemCheckPoint, *PKMemCheckPoint;

// local data or functions
static PKMemCheckPoint s_kmemCP[KMEM_CHECKPOINT_MAX_SUM];

#ifdef DEBUG_KMUTEX
static KMutex s_km(__FILE__, __LINE__);
#else 
static KMutex s_km;
#endif //DEBUG_KMUTEX

static UInt32 KMemAlignPageUp(UInt32 n)
{
    return (n + PAGE_SIZE - 1) & PAGE_MASK;
}

static Void TakeSnapShotOfCurKMem(const UInt32 uCP);

static Void ClearSnapShotOfCurKMem(const UInt32 uCP);

static Void CopyOneKMemBlock(PKMemBlock p1, const PKMemBlock p2);

static Boolean IsNewBlock(const PKMemBlock pHead, const PKMemBlock pblock);

static Void CmpABOneList(const PKMemBlock pAHead, const PKMemBlock pBHead,
    PKMemBlock *freedBlockBuf, PKMemBlock *allocedBlockBuf,
    PKMemBlock *freedPageBuf, PKMemBlock *allocedPageBuf,
    UInt32 *pFreedPageBufSize, UInt32 *pAllocedPageBufSize,
    UInt32 *pFreedBlockCount, UInt32 *pAllocedBlockCount,
    UInt32 *pFreedPageCount, UInt32 *pAllocedPageCount,
    Boolean bDetail);

static Void OutputCmpInfo(PKMemBlock *freedBlockBuf,
                         PKMemBlock *allocedBlockBuf,
                         PKMemBlock *freedPageBuf,
                         PKMemBlock *allocedPageBuf,
                         const UInt32 uFreedPageBufSize,
                         const UInt32 uAllocedPageBufSize,
                         const UInt32 uFreedBlockCount,
                         const UInt32 uAllocedBlockCount,
                         const UInt32 uFreedPageCount,
                         const UInt32 uAllocedPageCount,
                         const Boolean bDetail);

static Void PrintDetailInfo(PKMemBlock *buf, const UInt32 uBufSize,
                            const Boolean bAllocd, const Boolean bBlock);

static Boolean KMemStackCmp(const PKMemBlock pblock1, const PKMemBlock pblock2);

static UInt32 KMemPreOutPutStack(PKMemBlock *buf, const UInt32 uBufSize,
                                                const UInt32 uCurIndex);

static Void OutputCallStack(PKMemBlock pblock);

static Void MallocTempBuffer(UInt32 uCount, PKMemBlock **freedBuf,
    PKMemBlock **allocedBuf, Int32 *pOrder, Boolean *pKMalloc);

static Void FreeTempBuffer(PKMemBlock *freedBuf, PKMemBlock *allocedBuf,
    const Int32 nOrder, const Boolean bKMalloc);

//local macro
#define IS_ALLOCED_BLOCK(pAHead, pb) IsNewBlock(pAHead, pb)

#define IS_FREED_BLOCK(pBHead, pa) IsNewBlock(pBHead, pa)

//EXTERN
EXTERN_C uint_t * __cdecl kmalloc(uint_t);
EXTERN_C void __cdecl kfree(uint_t *);

EXTERN PKMemBlock g_kmemHashList[KMEM_DETECT_HASH_SIZE];
EXTERN UInt32 g_kmemCurBlockCount;
EXTERN UInt32 g_kmemCurAllocedBlockSize;
EXTERN UInt32 g_kmemCurPageAllotTimes;
EXTERN UInt32 g_kmemCurAllocedPageCount;

Void KMemCpInit();

Void KMemCpInit()
{
    memset(s_kmemCP, NULL, KMEM_CHECKPOINT_MAX_SUM * sizeof(PKMemCheckPoint));
}

EXTERN Void KMemSetCheckPoint(UInt32 uCP)
{
    s_km.Lock();

    assert((uCP != 0) && (uCP < KMEM_CHECKPOINT_MAX_SUM));
    if (s_kmemCP[uCP]) {
        ClearSnapShotOfCurKMem(uCP);
        kfree((uint_t *)(void *)s_kmemCP[uCP]);
        s_kmemCP[uCP] = NULL;
    }

    PKMemCheckPoint pcp = (PKMemCheckPoint)kmalloc( \
        SIZE_TO_INDEX(sizeof(KMemCheckPoint) + sizeof(UInt32)));
    ASSERT_MEM_NULL(pcp);

    s_kmemCP[uCP] = pcp;

    TakeSnapShotOfCurKMem(uCP);

    s_kmemCP[uCP]->uBlockCount = g_kmemCurBlockCount;
    s_kmemCP[uCP]->uAllocedSize = g_kmemCurAllocedBlockSize;
    s_kmemCP[uCP]->uPageAllotTimes = g_kmemCurPageAllotTimes;
    s_kmemCP[uCP]->uAllocedPageCount = g_kmemCurAllocedPageCount;
    s_kmemCP[uCP]->IsValid = 1;

    kprintf("\nAt CP%d, the current memory state is following:\n", uCP);
    kprintf("blockcount = %d\n", s_kmemCP[uCP]->uBlockCount);
    kprintf("blocksize = %d\n", s_kmemCP[uCP]->uAllocedSize);
    kprintf("pagecount = %d\n", s_kmemCP[uCP]->uAllocedPageCount);

    s_km.Unlock();
}

static Void ClearSnapShotOfCurKMem(const UInt32 uCP)
{
    if (NULL == s_kmemCP[uCP]->curCPHashList) return;

    PKMemBlock *pcpHash = s_kmemCP[uCP]->curCPHashList;
    for (UInt32 i = 0; i < KMEM_DETECT_HASH_SIZE; i++) {
        if (NULL == pcpHash[i]) continue;
        PKMemBlock pHead = pcpHash[i];
        PKMemBlock pb = NULL;
        pb = (PKMemBlock)(pHead->Next());
        while (pb != pHead) {
            pb->Detach();
            kfree((uint_t *)(void *)pb);
            pb = (PKMemBlock)(pHead->Next());
        }
        kfree((uint_t *)(void *)pHead);
    }
    KMemFreeKernelPage(pcpHash);
}

static Void TakeSnapShotOfCurKMem(const UInt32 uCP)
{
    assert((sizeof(PKMemBlock) * KMEM_DETECT_HASH_SIZE) == PAGE_SIZE);
    PKMemBlock *pcpHash = (PKMemBlock *)KMemAllocKernelPage();
    ASSERT_MEM_NULL(pcpHash);

    s_kmemCP[uCP]->curCPHashList = pcpHash;

    // dump current memory blocks
    for (UInt32 i = 0; i < KMEM_DETECT_HASH_SIZE; i++) {
        if (NULL == g_kmemHashList[i]) {
            pcpHash[i] = NULL;
            continue;
        }
        // dump the header of this list
        PKMemBlock pHead = (PKMemBlock)kmalloc( \
            SIZE_TO_INDEX(sizeof(KMemBlock) + sizeof(UInt32)));
        ASSERT_MEM_NULL(pHead);

        CopyOneKMemBlock(pHead, g_kmemHashList[i]);
        pHead->Initialize();
        pcpHash[i] = pHead;

        // copy another node of this list
        PKMemBlock pb = NULL;
        ForEachDLinkNode(PKMemBlock, pb, g_kmemHashList[i]) {
            PKMemBlock ptem = (PKMemBlock)kmalloc( \
                SIZE_TO_INDEX(sizeof(KMemBlock) + sizeof(UInt32)));
            ASSERT_MEM_NULL(ptem);

            CopyOneKMemBlock(ptem, pb);
            pHead->InsertLast(ptem);
        }
    }
}

static Void CopyOneKMemBlock(PKMemBlock p1, const PKMemBlock p2)
{
    memcpy((PKMemBlock)p1, (PKMemBlock)p2, sizeof(KMemBlock));
/*
    for (UInt32 i = 0; i < KMEM_DETECT_STACK_DEPTH; i++) {
        p1->callStack[i] = p2->callStack[i];
    }
    p1->pAddr = p2->pAddr;
    p1->uSize = p2->uSize;
    p1->pThread = p2->pThread;

    p1->Initialize();
*/
}

static Void MallocTempBuffer(UInt32 uCount, PKMemBlock **freedBuf,
    PKMemBlock **allocedBuf, Int32 *pOrder, Boolean *pKMalloc)
{
    if (0 == uCount) return;

    *pOrder = -1;
    UInt32 uSize = uCount * sizeof(PKMemBlock);
    if (uSize <= (PAGE_SIZE - sizeof(int))) {
        *freedBuf =
            (PKMemBlock *)kmalloc(SIZE_TO_INDEX(uSize + sizeof(UInt32)));
        ASSERT_MEM_NULL(*freedBuf);

        *allocedBuf = (PKMemBlock *)kmalloc( \
            SIZE_TO_INDEX(uSize + sizeof(UInt32)));
        ASSERT_MEM_NULL(*allocedBuf);

        *pKMalloc = TRUE;
    }
    else {
        *pOrder = GET_UP_ORDER(KMemAlignPageUp(uSize) / PAGE_SIZE);
        *freedBuf = (PKMemBlock *)KMemAllocKernelPages(*pOrder);
        ASSERT_MEM_NULL(*freedBuf);

        *allocedBuf = (PKMemBlock *)KMemAllocKernelPages(*pOrder);
        ASSERT_MEM_NULL(*allocedBuf);

        *pKMalloc = FALSE;
    }
}

static Void FreeTempBuffer(PKMemBlock *freedBuf, PKMemBlock *allocedBuf,
    const Int32 nOrder, const Boolean bKMalloc)
{
    if (NULL == freedBuf && NULL == allocedBuf) return;

    if (bKMalloc) {
        kfree((uint_t *)(void *)freedBuf);
        kfree((uint_t *)(void *)allocedBuf);
    }
    else {
        KMemFreeKernelPages(freedBuf, nOrder);
        KMemFreeKernelPages(allocedBuf, nOrder);
    }
}

EXTERN Void KMemCmpCP(UInt32 uCP1, UInt32 uCP2, Boolean bDetail)
{
    // uCP2 refer to uCP1
    assert(uCP1 < KMEM_CHECKPOINT_MAX_SUM);
    assert(uCP1 != 0 && uCP2 != 0);

    s_km.Lock();

    if (uCP1 == uCP2) {
        kprintf("\nWARNING: Two comparative CP is equal to CP%d\n", uCP1);
        s_km.Unlock();
        return;
    }
    if (NULL == s_kmemCP[uCP1]) {
        kprintf("\nWARNING: Now, CP%d is invalid!\n", uCP1);
        s_km.Unlock();
        DebugBreak();
        return;
    }
    if (uCP2 != KMEM_CURRENT_POINT && NULL == s_kmemCP[uCP2]) {
        kprintf("\nWARNING: Now, CP%d is invalid!\n", uCP2);
        s_km.Unlock();
        DebugBreak();
        return;
    }

    PKMemBlock *freedBlockBuf = NULL, *allocedBlockBuf = NULL;
    PKMemBlock *freedPageBuf = NULL, *allocedPageBuf = NULL;
    UInt32 uFpBufSize = 0, uApBufSize = 0;
    UInt32 uFreedBlockCount = 0, uAllocedBlockCount = 0;
    UInt32 uFreedPageCount = 0, uAllocedPageCount = 0;
    Boolean bBlockKMalloc, bPageKMalloc;
    Int32 nBlockOrder, nPageOrder;

    if (bDetail) {
        UInt32 uBlockCount2 = 0, uPageCount2 = 0;
        if (uCP2 == KMEM_CURRENT_POINT) {
            uBlockCount2 = g_kmemCurBlockCount;
            uPageCount2 = g_kmemCurPageAllotTimes;
        }
        else {
            uBlockCount2 = s_kmemCP[uCP2]->uBlockCount;
            uPageCount2 = s_kmemCP[uCP2]->uPageAllotTimes;
        }
        MallocTempBuffer(s_kmemCP[uCP1]->uBlockCount + uBlockCount2,
                        &freedBlockBuf, &allocedBlockBuf,
                        &nBlockOrder, &bBlockKMalloc);
        MallocTempBuffer(s_kmemCP[uCP1]->uPageAllotTimes + uPageCount2,
                        &freedPageBuf, &allocedPageBuf,
                        &nPageOrder, &bPageKMalloc);
    }

    PKMemBlock *pAHash = s_kmemCP[uCP1]->curCPHashList;
    PKMemBlock *pBHash = NULL;
    if (uCP2 == KMEM_CURRENT_POINT) {
        pBHash = g_kmemHashList;
    }
    else {
        pBHash = s_kmemCP[uCP2]->curCPHashList;
    }

    for (UInt32 i = 0; i < KMEM_DETECT_HASH_SIZE; i++) {
        CmpABOneList(pAHash[i], pBHash[i], freedBlockBuf, allocedBlockBuf,
                    freedPageBuf, allocedPageBuf, &uFpBufSize, &uApBufSize,
                    &uFreedBlockCount, &uAllocedBlockCount,
                    &uFreedPageCount, &uAllocedPageCount, bDetail);
    }

    OutputCmpInfo(freedBlockBuf, allocedBlockBuf, freedPageBuf,
        allocedPageBuf,
        uFpBufSize, uApBufSize, uFreedBlockCount, uAllocedBlockCount,
        uFreedPageCount, uAllocedPageCount, bDetail);

    if (bDetail) {
        FreeTempBuffer(freedBlockBuf, allocedBlockBuf,
                        nBlockOrder, bBlockKMalloc);
        FreeTempBuffer(freedPageBuf, allocedPageBuf,
                        nPageOrder, bPageKMalloc);
    }
    s_km.Unlock();
}

static Void OutputCmpInfo(PKMemBlock *freedBlockBuf,
    PKMemBlock *allocedBlockBuf,
    PKMemBlock *freedPageBuf,
    PKMemBlock *allocedPageBuf,
    const UInt32 uFreedPageBufSize,
    const UInt32 uAllocedPageBufSize,
    const UInt32 uFreedBlockCount,
    const UInt32 uAllocedBlockCount,
    const UInt32 uFreedPageCount,
    const UInt32 uAllocedPageCount,
    const Boolean bDetail)
{
    kprintf("\n*****************KERNEL MEMORY DETECT*******************\n");

    kprintf("alloced block sum: %d\n", uAllocedBlockCount);
    kprintf("freed block sum: %d\n", uFreedBlockCount);
    kprintf("alloced page count: %d\n", uAllocedPageCount);
    kprintf("freed page count: %d\n", uFreedPageCount);

    if (bDetail) {
        kprintf("\nDetail info following:\n");
        PrintDetailInfo(allocedBlockBuf, uAllocedBlockCount, TRUE, TRUE);
        kprintf("\n------------------------------------------------------\n");
        PrintDetailInfo(allocedPageBuf, uAllocedPageBufSize, TRUE, FALSE);
        kprintf("\n======================================================\n");
        PrintDetailInfo(freedBlockBuf, uFreedBlockCount, FALSE, TRUE);
        kprintf("\n------------------------------------------------------\n");
        PrintDetailInfo(freedPageBuf, uFreedPageBufSize, FALSE, FALSE);
    }

    kprintf("\n********************************************************\n");
}

static Void PrintDetailInfo(PKMemBlock *buf, const UInt32 uBufSize,
    const Boolean bAllocd, const Boolean bBlock)
{
    char chFlag[20];
    char chSize[20];
    if (bBlock) {
        strcpy(chSize, "size");
        if (bAllocd)
            strcpy(chFlag, "alloced blocks: ");
        else
            strcpy(chFlag, "freed blocks: ");
    }
    else {
        strcpy(chSize, "page count");
        if (bAllocd)
            strcpy(chFlag, "alloced pages: ");
        else
            strcpy(chFlag, "freed pages: ");
    }

    kprintf("\n%s\n", chFlag);
    for (UInt32 i = 0; i < uBufSize; i++) {
        if (NULL == buf[i]) continue;
        kprintf("%saddr = %x, %s = %d, thread = %x\n", chFlag, \
            buf[i]->pAddr, chSize, buf[i]->uSize, buf[i]->pThread);
        UInt32 uSameStack = KMemPreOutPutStack(buf, uBufSize, i);
        if (0 == uSameStack) {
            kprintf("       callstack:");
        }
        else {
            kprintf("       callstack: (same statcks = %d)\n",
                uSameStack + 1);
        }
        OutputCallStack(buf[i]);
    }
}

static UInt32 KMemPreOutPutStack(PKMemBlock *buf, const UInt32 uBufSize,
    const UInt32 uCurIndex)
{
    // the last one
    if (uCurIndex == uBufSize - 1) return 0;

    PKMemBlock pblock1 = buf[uCurIndex];
    UInt32 nCount = 0;

    for (UInt32 i = uCurIndex + 1; i < uBufSize; i++) {
        if (NULL == buf[i]) continue;
        if (KMemStackCmp(pblock1, buf[i])) {
            nCount++;
            buf[i] = NULL;
        }
    }

    return nCount;
}

static Boolean KMemStackCmp(const PKMemBlock pblock1, const PKMemBlock pblock2)
{
    if (!memcmp((int *)pblock1->callStack,
        (int *)pblock2->callStack,
        sizeof(pblock1->callStack))) {
        return TRUE;
    }

    return FALSE;
}

static Void OutputCallStack(PKMemBlock pblock)
{
    for (UInt32 ui = 0; ui < KMEM_DETECT_STACK_DEPTH; ui++) {
        if (!pblock->callStack[ui]) continue;
        kprintf("%d : 0x%08x   ", ui, pblock->callStack[ui]);
    }
    kprintf("\n\n");
}

static Boolean IsNewBlock(const PKMemBlock pHead, const PKMemBlock pblock)
{
    if (NULL == pHead) return TRUE;
    if (pHead->pAddr == pblock->pAddr) return FALSE;
    PKMemBlock p;
    ForEachDLinkNode(PKMemBlock, p, pHead) {
        if (p->pAddr == pblock->pAddr) return FALSE;
    }
    return TRUE;
}

static Void CmpABOneList(const PKMemBlock pAHead, const PKMemBlock pBHead,
    PKMemBlock *freedBlockBuf, PKMemBlock *allocedBlockBuf,
    PKMemBlock *freedPageBuf, PKMemBlock *allocedPageBuf,
    UInt32 *pFreedPageBufSize, UInt32 *pAllocedPageBufSize,
    UInt32 *pFreedBlockCount, UInt32 *pAllocedBlockCount,
    UInt32 *pFreedPageCount, UInt32 *pAllocedPageCount,
    Boolean bDetail)
{
    if (NULL == pAHead && NULL == pBHead) return;

    if (NULL == pAHead && pBHead) {
        PKMemBlock p = pBHead;
        do {
            if (bDetail) {
                if (!p->bPhysPage) {
                    allocedBlockBuf[*pAllocedBlockCount] = p;
                }
                else {
                    allocedPageBuf[*pAllocedPageBufSize] = p;
                }
            }
            if (!p->bPhysPage) {
                (*pAllocedBlockCount)++;
            }
            else {
                (*pAllocedPageBufSize)++;
                *pAllocedPageCount += p->uSize;
            }
            p = (PKMemBlock)(p->Next());
        } while (p != pBHead);

        return;
    }

    if (pAHead && NULL == pBHead) {
        PKMemBlock p = pAHead;
        do {
            if (bDetail) {
                if (!p->bPhysPage) {
                    freedBlockBuf[*pFreedBlockCount] = p;
                }
                else {
                    freedPageBuf[*pFreedPageBufSize] = p;
                }
            }
            if (!p->bPhysPage) {
                (*pFreedBlockCount)++;
            }
            else {
                (*pFreedPageBufSize)++;
                *pFreedPageCount += p->uSize;
            }
            p = (PKMemBlock)(p->Next());
        } while (p != pAHead);

        return;
    }

    // alloced block
    PKMemBlock p = pBHead;
    do {
        if (IS_ALLOCED_BLOCK(pAHead, p)) {
            if (bDetail) {
                if (!p->bPhysPage) {
                    allocedBlockBuf[*pAllocedBlockCount] = p;
                }
                else {
                    allocedPageBuf[*pAllocedPageBufSize] = p;
                }
            }
            if (!p->bPhysPage) {
                (*pAllocedBlockCount)++;
            }
            else {
                (*pAllocedPageBufSize)++;
                *pAllocedPageCount += p->uSize;
            }
        }
        p = (PKMemBlock)(p->Next());
    } while (p != pBHead);

    // freed block
    p = pAHead;
    do {
        if (IS_FREED_BLOCK(pBHead, p)) {
            if (bDetail) {
                if (!p->bPhysPage) {
                    freedBlockBuf[*pFreedBlockCount] = p;
                }
                else {
                    freedPageBuf[*pFreedPageBufSize] = p;
                }
            }
            if (!p->bPhysPage) {
                (*pFreedBlockCount)++;
            }
            else {
                (*pFreedPageBufSize)++;
                *pFreedPageCount += p->uSize;
            }
        }
        p = (PKMemBlock)(p->Next());
    } while (p != pAHead);
}

#endif //KERNEL_MEMORY_DETECT
